<?php
/**
 * Format Class
 * Include functions for formating data to insert to Database or view to User
 *
 * @since 1.0
 * @access public
 * @author trung, KeiMOon and various peoples
 *
 * @package OWL
 * @subpackage Helper
 */
class Owl_Format {
    
    /**
     * Escaping for HTML blocks.
     *
     * @since 1.0
     * @author wordpress
     *
     * @param string $text
     * @return string
     */
    static function esc_html( $text ) {
        $safe_text = self::check_invalid_utf8($text);
        $safe_text = self::_specialchars( $safe_text, ENT_QUOTES );
        return $safe_text;
    }

    static function esc_html_deep( $value ) {
        $value = is_array($value) ? array_map(array(self,'esc_html_deep'), $value) : self::esc_html($value);
        return $value;
    }

    static function esc_html_json( $text ) {
        return htmlspecialchars( $text, ENT_NOQUOTES );
    }

    /**
     * Checks for invalid UTF8 in a string.
     *
     * @since 1.0
     * @author wordpress
     * Change wp prefix to owl
     *
     * @param string $string The text which is to be checked.
     * @param boolean $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
     * @return string The checked text.
     */
    static function check_invalid_utf8( $string, $strip = false ) {
        $string = (string) $string;

        if ( 0 === strlen( $string ) ) {
            return '';
        }

        // Store the site charset as a static to avoid multiple calls to get_option()
        static $is_utf8;
        if ( !isset( $is_utf8 ) ) {
            //$is_utf8 = in_array( get_option( 'blog_charset' ), array( 'utf8', 'utf-8', 'UTF8', 'UTF-8' ) );
        }
        if ( !$is_utf8 ) {
            return $string;
        }

        // Check for support for utf8 in the installed PCRE library once and store the result in a static
        static $utf8_pcre;
        if ( !isset( $utf8_pcre ) ) {
            $utf8_pcre = @preg_match( '/^./u', 'a' );
        }
        // We can't demand utf8 in the PCRE installation, so just return the string in those cases
        if ( !$utf8_pcre ) {
            return $string;
        }

        // preg_match fails when it encounters invalid UTF8 in $string
        if ( 1 === @preg_match( '/^./us', $string ) ) {
            return $string;
        }

        // Attempt to strip the bad chars if requested (not recommended)
        if ( $strip && function_exists( 'iconv' ) ) {
            return iconv( 'utf-8', 'utf-8', $string );
        }
        return '';
    }

    /**
     * Converts a number of special characters into their HTML entities.
     *
     * Specifically deals with: &, <, >, ", and '.
     *
     * $quote_style can be set to ENT_COMPAT to encode " to
     * &quot;, or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded.
     *
     * @since 1.0
     * @author wordpress
     * Change prefix wp to owl
     *
     * @param string $string The text which is to be encoded.
     * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES.
     * @param string $charset Optional. The character encoding of the string. Default is false.
     * @param boolean $double_encode Optional. Whether or not to encode existing html entities. Default is false.
     * @return string The encoded text with HTML entities.
     */
    static function _specialchars( $string, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) {
        $string = (string) $string;

        if ( 0 === strlen( $string ) ) {
            return '';
        }

        // Don't bother if there are no specialchars - saves some processing
        if ( !preg_match( '/[&<>"\']/', $string ) ) {
            return $string;
        }

        // Account for the previous behaviour of the function when the $quote_style is not an accepted value
        if ( empty( $quote_style ) ) {
            $quote_style = ENT_NOQUOTES;
        } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
            $quote_style = ENT_QUOTES;
        }

        // Store the site charset as a static to avoid multiple calls to wp_load_alloptions()
        if ( !$charset ) {
            static $_charset;
            if ( !isset( $_charset ) ) {
                //$alloptions = wp_load_alloptions();
                //$_charset = isset( $alloptions['blog_charset'] ) ? $alloptions['blog_charset'] : '';
            }
            //$charset = $_charset;
        }
        if ( in_array( $charset, array( 'utf8', 'utf-8', 'UTF8' ) ) ) {
            $charset = 'UTF-8';
        }

        $_quote_style = $quote_style;

        if ( $quote_style === 'double' ) {
            $quote_style = ENT_COMPAT;
            $_quote_style = ENT_COMPAT;
        } elseif ( $quote_style === 'single' ) {
            $quote_style = ENT_NOQUOTES;
        }

        // Handle double encoding ourselves
        if ( !$double_encode ) {
            $string = self::specialchars_decode( $string, $_quote_style );
            $string = preg_replace( '/&(#?x?[0-9a-z]+);/i', '|wp_entity|$1|/wp_entity|', $string );
        }

        $string = @htmlspecialchars( $string, $quote_style, $charset );

        // Handle double encoding ourselves
        if ( !$double_encode ) {
            $string = str_replace( array( '|wp_entity|', '|/wp_entity|' ), array( '&', ';' ), $string );
        }

        // Backwards compatibility
        if ( 'single' === $_quote_style ) {
            $string = str_replace( "'", '&#039;', $string );
        }
        
        return $string;
    }

    /**
     * Checks and cleans a URL.
     *
     * A number of characters are removed from the URL. If the URL is for displaying
     * (the default behaviour) amperstands are also replaced. The 'esc_url' filter
     * is applied to the returned cleaned URL.
     *
     * @since 1.0
     * @author wordpress
     * @uses esc_url()
     * @uses wp_kses_bad_protocol() To only permit protocols in the URL set
     *		via $protocols or the common ones set in the function.
     *
     * @param string $url The URL to be cleaned.
     * @param array $protocols Optional. An array of acceptable protocols.
     *		Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet' if not set.
     * @return string The cleaned $url after the 'cleaned_url' filter is applied.
     */
    static function esc_url( $url, $protocols = null ) {
         return self::clean_url( $url, $protocols, 'display' );
    }

    /**
     * Checks and cleans a URL.
     *
     * A number of characters are removed from the URL. If the URL is for displaying
     * (the default behaviour) amperstands are also replaced. The 'esc_url' filter
     * is applied to the returned cleaned URL.
     *
     * @since 1.0
     * @author wordpress
     * @uses wp_kses_bad_protocol() To only permit protocols in the URL set
     *		via $protocols or the common ones set in the function.
     *
     * @param string $url The URL to be cleaned.
     * @param array $protocols Optional. An array of acceptable protocols.
     *		Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet' if not set.
     * @param string $context Optional. How the URL will be used. Default is 'display'.
     * @return string The cleaned $url after the 'cleaned_url' filter is applied.
     */
    static function clean_url( $url, $protocols = null, $context = 'display' ) {
        $original_url = $url;
        
        if ('' == $url) return $url;
        $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
        $strip = array('%0d', '%0a');
        $url = str_replace($strip, '', $url);
        $url = str_replace(';//', '://', $url);
        /* If the URL doesn't appear to contain a scheme, we
         * presume it needs http:// appended (unless a relative
         * link starting with / or a php file).
         */
        if ( strpos($url, ':') === false &&
            substr( $url, 0, 1 ) != '/' && substr( $url, 0, 1 ) != '#' && !preg_match('/^[a-z0-9-]+?\.php/i', $url) )
            $url = 'http://' . $url;

        // Replace ampersands and single quotes only when displaying.
        if ( 'display' == $context ) {
            $url = preg_replace('/&([^#])(?![a-z]{2,8};)/', '&#038;$1', $url);
            $url = str_replace( "'", '&#039;', $url );
        }

        if ( !is_array($protocols) )
            $protocols = array('http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet');
        if ( kses_bad_protocol( $url, $protocols ) != $url )
           return '';
        return $url;
        //return apply_filters('clean_url', $url, $original_url, $context);

    }

    /**
     * Converts a number of HTML entities into their special characters.
     *
     * Specifically deals with: &, <, >, ", and '.
     *
     * $quote_style can be set to ENT_COMPAT to decode " entities,
     * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded.
     *
     * @since 1.0
     * @author wordpress
     * Change prefix from wp to owl
     *
     * @param string $string The text which is to be decoded.
     * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old _wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES.
     * @return string The decoded text without HTML entities.
     */
    static function specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) {
        $string = (string) $string;

        if ( 0 === strlen( $string ) ) {
            return '';
        }

        // Don't bother if there are no entities - saves a lot of processing
        if ( strpos( $string, '&' ) === false ) {
            return $string;
        }

        // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
        if ( empty( $quote_style ) ) {
            $quote_style = ENT_NOQUOTES;
        } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
            $quote_style = ENT_QUOTES;
        }

        // More complete than get_html_translation_table( HTML_SPECIALCHARS )
        $single = array( '&#039;'  => '\'', '&#x27;' => '\'' );
        $single_preg = array( '/&#0*39;/'  => '&#039;', '/&#x0*27;/i' => '&#x27;' );
        $double = array( '&quot;' => '"', '&#034;'  => '"', '&#x22;' => '"' );
        $double_preg = array( '/&#0*34;/'  => '&#034;', '/&#x0*22;/i' => '&#x22;' );
        $others = array( '&lt;'   => '<', '&#060;'  => '<', '&gt;'   => '>', '&#062;'  => '>', '&amp;'  => '&', '&#038;'  => '&', '&#x26;' => '&' );
        $others_preg = array( '/&#0*60;/'  => '&#060;', '/&#0*62;/'  => '&#062;', '/&#0*38;/'  => '&#038;', '/&#x0*26;/i' => '&#x26;' );

        if ( $quote_style === ENT_QUOTES ) {
            $translation = array_merge( $single, $double, $others );
            $translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
        } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) {
            $translation = array_merge( $double, $others );
            $translation_preg = array_merge( $double_preg, $others_preg );
        } elseif ( $quote_style === 'single' ) {
            $translation = array_merge( $single, $others );
            $translation_preg = array_merge( $single_preg, $others_preg );
        } elseif ( $quote_style === ENT_NOQUOTES ) {
            $translation = $others;
            $translation_preg = $others_preg;
        }

        // Remove zero padding on numeric entities
        $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );

        // Replace characters according to translation table
        return strtr( $string, $translation );
    }

    /**
     * Escapes text for SQL LIKE special characters % and _.
     *
     * @since 1.0
     *
     * @param string $text The text to be escaped.
     * @return string text, safe for inclusion in LIKE query.
     */
    static function like_escape($text) {
            return str_replace(array("%", "_"), array("\\%", "\\_"), $text);
    }

    /**
     * Escaping for HTML attributes.
     *
     * @since 2.8.0
     *
     * @param string $text
     * @return string
     */
    static function esc_attr( $text ) {
            $safe_text = self::check_invalid_utf8($text);
            $safe_text = self::_specialchars($safe_text, ENT_QUOTES );
            return $safe_text;
            //return apply_filters( 'attribute_escape', $safe_text, $text );
    }

   static function encode_xml($text) {
       $safe_text = str_replace(array("<", ">", "&"), array("{{opentag}}","{{closetag}}", "{{ampersand}}"), $text);
       return trim(self::esc_html($safe_text));
   }

   static function decode_xml($text) {
       return str_replace(array("{{opentag}}","{{closetag}}", "{{ampersand}}"), array("&lt;", "&gt;", "&amp;"), $text);
   }

    /**
     * Balances tags of string using a modified stack.
     *
     * @since 2.0.4
     *
     * @author Leonard Lin <leonard@acm.org>
     * @license GPL v2.0
     * @copyright November 4, 2001
     * @version 1.1
     * @todo Make better - change loop condition to $text in 1.2
     * @internal Modified by Scott Reilly (coffee2code) 02 Aug 2004
     *		1.1  Fixed handling of append/stack pop order of end text
     *			 Added Cleaning Hooks
     *		1.0  First Version
     *
     * @param string $text Text to be balanced.
     * @return string Balanced text.
     */
    static function force_balance_tags( $text ) {
        $tagstack = array(); $stacksize = 0; $tagqueue = ''; $newtext = '';
        $single_tags = array('br', 'hr', 'img', 'input'); //Known single-entity/self-closing tags
        $nestable_tags = array('blockquote', 'div', 'span'); //Tags that can be immediately nested within themselves

        # bug fix for comments - in case you REALLY meant to type '< !--'
        $text = str_replace('< !--', '<    !--', $text);
        # bug fix for LOVE <3 (and other situations with '<' before a number)
        $text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);

        while (preg_match("/<(\/?\w*)\s*([^>]*)>/",$text,$regex)) {
            $newtext .= $tagqueue;

            $i = strpos($text,$regex[0]);
            $l = strlen($regex[0]);

            // clear the shifter
            $tagqueue = '';
            // Pop or Push
            if ( isset($regex[1][0]) && '/' == $regex[1][0] ) { // End Tag
                $tag = strtolower(substr($regex[1],1));
                // if too many closing tags
                if($stacksize <= 0) {
                    $tag = '';
                    //or close to be safe $tag = '/' . $tag;
                }
                // if stacktop value = tag close value then pop
                else if ($tagstack[$stacksize - 1] == $tag) { // found closing tag
                    $tag = '</' . $tag . '>'; // Close Tag
                    // Pop
                    array_pop ($tagstack);
                    $stacksize--;
                } else { // closing tag not at top, search for it
                    for ($j=$stacksize-1;$j>=0;$j--) {
                        if ($tagstack[$j] == $tag) {
                        // add tag to tagqueue
                            for ($k=$stacksize-1;$k>=$j;$k--){
                                $tagqueue .= '</' . array_pop ($tagstack) . '>';
                                $stacksize--;
                            }
                            break;
                        }
                    }
                    $tag = '';
                }
            } else { // Begin Tag
                $tag = strtolower($regex[1]);

                // Tag Cleaning

                // If self-closing or '', don't do anything.
                if((substr($regex[2],-1) == '/') || ($tag == '')) {
                }
                // ElseIf it's a known single-entity tag but it doesn't close itself, do so
                elseif ( in_array($tag, $single_tags) ) {
                    $regex[2] .= '/';
                } else {	// Push the tag onto the stack
                    // If the top of the stack is the same as the tag we want to push, close previous tag
                    if (($stacksize > 0) && !in_array($tag, $nestable_tags) && ($tagstack[$stacksize - 1] == $tag)) {
                        $tagqueue = '</' . array_pop ($tagstack) . '>';
                        $stacksize--;
                    }
                    $stacksize = array_push ($tagstack, $tag);
                }

                // Attributes
                $attributes = $regex[2];
                if($attributes) {
                    $attributes = ' '.$attributes;
                }
                $tag = '<'.$tag.$attributes.'>';
                //If already queuing a close tag, then put this tag on, too
                if ($tagqueue) {
                    $tagqueue .= $tag;
                    $tag = '';
                }
            }
            $newtext .= substr($text,0,$i) . $tag;
            $text = substr($text,$i+$l);
        }

        // Clear Tag Queue
        $newtext .= $tagqueue;

        // Add Remaining text
        $newtext .= $text;

        // Empty Stack
        while($x = array_pop($tagstack)) {
            $newtext .= '</' . $x . '>'; // Add remaining tags to close
        }

        // WP fix for the bug with HTML comments
        $newtext = str_replace("< !--","<!--",$newtext);
        $newtext = str_replace("<    !--","< !--",$newtext);

        return $newtext;
    }

    /**
     * Checks to see if a string is utf8 encoded.
     *
     * NOTE: This function checks for 5-Byte sequences, UTF8
     *       has Bytes Sequences with a maximum length of 4.
     *
     * @author bmorel at ssi dot fr (modified)
     * @since 1.0
     *
     * @param string $str The string to be checked
     * @return bool True if $str fits a UTF-8 model, false otherwise.
     */
    static function seems_utf8($str) {
        $length = strlen($str);
        for ($i=0; $i < $length; $i++) {
            $c = ord($str[$i]);
            if ($c < 0x80) $n = 0; # 0bbbbbbb
            elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
            elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
            elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
            elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
            elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
            else return false; # Does not match any model
            for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
                if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
                    return false;
            }
        }
        return true;
    }

    /**
     * Navigates through an array and removes slashes from the values.
     *
     * If an array is passed, the array_map() function causes a callback to pass the
     * value back to the function. The slashes from this value will removed.
     *
     *
     * @param array|string $value The array or string to be striped.
     * @return array|string Stripped array (or string in the callback).
     */
    static function stripslashes_deep($value) {
        $value = is_array($value) ? array_map(array(self, 'stripslashes_deep'), $value) : stripslashes($value);
        return $value;
    }

    /**
     * Parses a string into variables to be stored in an array.
     *
     * Uses {@link http://www.php.net/parse_str parse_str()} and stripslashes if
     * {@link http://www.php.net/magic_quotes magic_quotes_gpc} is on..
     *
     * @param string $string The string to be parsed.
     * @param array $array Variables will be stored in this array.
     */
    static function parse_str( $string, &$array ) {
            parse_str( $string, $array );
            if ( get_magic_quotes_gpc() )
                    $array = self::stripslashes_deep( $array );
    }

    /**
     * Navigates through an array and encodes the values to be used in a URL.
     *
     * Uses a callback to pass the value of the array back to the function as a
     * string.
     *
     * @since 2.2.0
     *
     * @param array|string $value The array or string to be encoded.
     * @return array|string $value The encoded array (or string from the callback).
     */
    static function urlencode_deep($value) {
            $value = is_array($value) ? array_map(array(self,'urlencode_deep'), $value) : urlencode($value);
            return $value;
    }

    /**
     * Converts all accent characters to ASCII characters.
     *
     * If there are no accent characters, then the string given is just returned.
     *
     * @since 1.0
     *
     * @param string $string Text that might have accent characters
     * @return string Filtered string with replaced "nice" characters.
     */
    static function remove_accents($string) {
        if ( !preg_match('/[\x80-\xff]/', $string) )
            return $string;

        if (self::seems_utf8($string)) {
            $chars = array(
            // Decompositions for Latin-1 Supplement
            chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
            chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
            chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
            chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
            chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
            chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
            chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
            chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
            chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
            chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
            chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
            chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
            chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
            chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
            chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
            chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
            chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
            chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
            chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
            chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
            chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
            chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
            chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
            chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
            chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
            chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
            chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
            chr(195).chr(191) => 'y',
            // Decompositions for Latin Extended-A
            chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
            chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
            chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
            chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
            chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
            chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
            chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
            chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
            chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
            chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
            chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
            chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
            chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
            chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
            chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
            chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
            chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
            chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
            chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
            chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
            chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
            chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
            chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
            chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
            chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
            chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
            chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
            chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
            chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
            chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
            chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
            chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
            chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
            chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
            chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
            chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
            chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
            chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
            chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
            chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
            chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
            chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
            chr(197).chr(148) => 'R',chr(197).chr(149) => 'r',
            chr(197).chr(150) => 'R',chr(197).chr(151) => 'r',
            chr(197).chr(152) => 'R',chr(197).chr(153) => 'r',
            chr(197).chr(154) => 'S',chr(197).chr(155) => 's',
            chr(197).chr(156) => 'S',chr(197).chr(157) => 's',
            chr(197).chr(158) => 'S',chr(197).chr(159) => 's',
            chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
            chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
            chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
            chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
            chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
            chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
            chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
            chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
            chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
            chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
            chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
            chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
            chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
            chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
            chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
            chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
            //Vietnamese accent
            'ơ' => 'o', 'ư' => 'u', 'Ơ' => 'O', 'Ư' => 'U',
            'ả' => 'a', 'ạ' => 'a', 'Ả' => 'A', 'Ạ' => 'A',
            'ắ' => 'a', 'ằ' => 'a', 'ẳ' => 'a', 'ẵ' => 'a', 'ặ' => 'a',
            'Ắ' => 'A', 'Ằ' => 'A', 'Ẳ' => 'A', 'Ẵ' => 'A', 'Ặ' => 'A',
            'ấ' => 'a', 'ầ' => 'a', 'ẩ' => 'a', 'ẫ' => 'a', 'ậ' => 'a',
            'Ấ' => 'A', 'Ầ' => 'A', 'Ẩ' => 'A', 'Ẫ' => 'A', 'Ậ' => 'A',
            'ẻ' => 'e', 'ẽ' => 'e', 'ẹ' => 'e',
            'Ẻ' => 'E', 'Ẽ' => 'E', 'Ẹ' => 'E',
            'ế' => 'e', 'ề' => 'e', 'ể' => 'e', 'ễ' => 'e', 'ệ' => 'e',
            'Ế' => 'E', 'Ề' => 'E', 'Ể' => 'E', 'Ễ' => 'E', 'Ệ' => 'E',
            'ỉ' => 'i', 'ị' => 'i', 'Ỉ' => 'I', 'Ị' => 'I',
            'ỏ' => 'o', 'ọ' => 'o', 'Ỏ' => 'O', 'Ọ' => 'O',
            'ố' => 'o', 'ồ' => 'o', 'ổ' => 'o', 'ỗ' => 'o', 'ộ' => 'o',
            'Ố' => 'O', 'Ồ' => 'O', 'Ổ' => 'O', 'Ỗ' => 'O', 'Ộ' => 'O',
            'ớ' => 'o', 'ờ' => 'o', 'ở' => 'o', 'ỡ' => 'o', 'ợ' => 'o',
            'Ớ' => 'O', 'Ờ' => 'O', 'Ở' => 'O', 'Ỡ' => 'O', 'Ợ' => 'O',
            'ủ' => 'u', 'ụ' => 'u', 'Ủ' => 'U', 'Ụ' => 'U',
            'ứ' => 'u', 'ừ' => 'u', 'ử' => 'u', 'ữ' => 'u', 'ự' => 'u',
            'Ứ' => 'U', 'Ừ' => 'U', 'Ử' => 'U', 'Ữ' => 'U', 'Ự' => 'U',
            'ỳ' => 'y', 'ỷ' => 'y', 'ỹ' => 'y', 'ỵ' => 'y',
            'Ỳ' => 'Y', 'Ỷ' => 'Y', 'Ỹ' => 'Y', 'Ỵ' => 'Y',
            // Euro Sign
            chr(226).chr(130).chr(172) => 'E',
            // GBP (Pound) Sign
            chr(194).chr(163) => '');

            $string = strtr($string, $chars);
        } else {
            // Assume ISO-8859-1 if not UTF-8
            $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
                .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
                .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
                .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
                .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
                .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
                .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
                .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
                .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
                .chr(252).chr(253).chr(255);

            $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";

            $string = strtr($string, $chars['in'], $chars['out']);
            $double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
            $double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
            $string = str_replace($double_chars['in'], $double_chars['out'], $string);
        }

        return $string;
    }

    /**
     * Escape string to fit in a regular expression
     * These characters will be escape: \, |, (, ), [, {, ^, $, *, ?, +, .
     *
     * @param string $string Original string
     * @return string Escaped string
     */
    static function escape_regex($string) {
        $to_escape = array('\\','/','|','(',')','[','{','^','$','*','?','+','.');
        $escaped = array("\\\\",'\/',"\\|","\(","\)","\[","\{","\^","\$","\*","\?","\+","\.");
        return str_replace($to_escape, $escaped, $string);
    }

    /**
     * Add leading zeros when necessary.
     *
     * If you set the threshold to '4' and the number is '10', then you will get
     * back '0010'. If you set the number to '4' and the number is '5000', then you
     * will get back '5000'.
     *
     * Uses sprintf to append the amount of zeros based on the $threshold parameter
     * and the size of the number. If the number is large enough, then no zeros will
     * be appended.
     *
     * @since 1.0
     *
     * @param mixed $number Number to append zeros to if not greater than threshold.
     * @param int $threshold Digit places number needs to be to not have zeros added.
     * @return string Adds leading zeros to number if needed.
     */
    function zeroise($number, $threshold) {
            return sprintf('%0'.$threshold.'s', $number);
    }

    /**
     * Adds backslashes before letters and before a number at the start of a string.
     *
     * @since 0.71
     *
     * @param string $string Value to which backslashes will be added.
     * @return string String with backslashes inserted.
     */
    function backslashit($string) {
            $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', $string);
            $string = preg_replace('/([a-z])/i', '\\\\\1', $string);
            return $string;
    }


    /**
     * Create a file name string from a string, the result only allows
     * non-accent alphabet character, hyphen and number. This function also do the
     * letf trimming and right trimming action
     * 
     * @param string $string
     * @return string $result String that remove all accent and special character  
     */
    static function make_file_name($string) {
        $result = self::remove_accents($string);
        $result = preg_replace("/[^\w_\-\s]/", "", $result);
        $result = preg_replace("/[\s_]+/","-",$result);
        $result = preg_replace("/^-+/","",$result);
        $result = preg_replace("/-+$/","",$result);
        $result = preg_replace("/-+/","-",$result);
        return strtolower($result);
    }

    /**
     * Callback for increase file name function
     *
     * @param array $matches
     * @return string
     */
    static function _increase_file_name_call_back($matches) {
        return $matches[1] + 1;
    }

    /**
     * Increase file name count if this file name existed
     *
     * @param string $string The file name
     * @return string
     */
    static function increase_file_name($string) {
        $string = trim($string);
        if (!preg_match("/(\d+)$/", $string)) {
            return $string."-1";
        }
        return preg_replace_callback("/(\d+)$/", array("Owl_Format", "_increase_file_name_call_back"), $string);
    }

    /**
     * Callback to convert URI match to HTML A element.
     *
     * Regex callback for {@link make_clickable()}.
     *
     * @since 1.0
     * @access private
     *
     * @param array $matches Single Regex Match.
     * @return string HTML A element with URI address.
     */
    private function _make_url_clickable_cb($matches) {
        $url = $matches[2];
        $url = self::esc_url($url);
        if ( empty($url) )
            return $matches[0];
        return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>";
    }

    /**
     * Callback to convert URL match to HTML A element.
     *
     * Regex callback for {@link make_clickable()}.
     *
     * @since 1.0
     * @access private
     *
     * @param array $matches Single Regex Match.
     * @return string HTML A element with URL address.
     */
    private function _make_web_ftp_clickable_cb($matches) {
        $ret = '';
        $dest = $matches[2];
        $dest = 'http://' . $dest;
        $dest = self::esc_url($dest);
        if ( empty($dest) )
            return $matches[0];
        // removed trailing [,;:] from URL
        if ( in_array(substr($dest, -1), array('.', ',', ';', ':')) === true ) {
            $ret = substr($dest, -1);
            $dest = substr($dest, 0, strlen($dest)-1);
        }
        return $matches[1] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>" . $ret;
    }

    /**
     * Callback to convert email address match to HTML A element.
     *
     * Regex callback for {@link make_clickable()}.
     *
     * @since 1.0
     * @access private
     *
     * @param array $matches Single Regex Match.
     * @return string HTML A element with email address.
     */
    private function _make_email_clickable_cb($matches) {
        $email = $matches[2] . '@' . $matches[3];
        return $matches[1] . "<a href=\"mailto:$email\">$email</a>";
    }

    /**
     * Convert plaintext URI to HTML links.
     *
     * Converts URI, www and ftp, and email addresses. Finishes by fixing links
     * within links.
     *
     * @since 1.0
     *
     * @param string $ret Content to convert URIs.
     * @return string Content with converted URIs.
     */
    static function make_clickable($ret) {
        $ret = ' ' . $ret;
        // in testing, using arrays here was found to be faster
        $ret = preg_replace_callback('#(?<=[\s>])(\()?([\w]+?://(?:[\w\\x80-\\xff\#$%&~/\-=?@\[\](+]|[.,;:](?![\s<])|(?(1)\)(?![\s<])|\)))+)#is', array('Owl_Format','_make_url_clickable_cb'), $ret);
        $ret = preg_replace_callback('#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', array('Owl_Format','_make_web_ftp_clickable_cb'), $ret);
        $ret = preg_replace_callback('#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', array('Owl_Format','_make_email_clickable_cb'), $ret);
        // this one is not in an array because we need it to run last, for cleanup of accidental links within links
        $ret = preg_replace("#(<a( [^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i", "$1$3</a>", $ret);
        $ret = trim($ret);
        return $ret;
    }

    /**
     * Trim a string based on length, support multi-byte string and HTML 
     * entities
     *
     * @param string $text
     * @param int $length
     * @return int $result 
     */
    static function trim_text($text, $length) {
        $text_array = preg_split("//u",$text,-1,PREG_SPLIT_NO_EMPTY);
        $text_length = count($text_array);
        $current_char = 0;
        $current_length = 0;
        $result = "";
        $special_char = false;
        while ($current_char < $text_length && $current_length < $length) {
            if ($special_char) {
                if ($text_array[$current_char] == ";") {
                    $current_length++;
                    $special_char = false;
                }
            }
            else {
                if ($text_array[$current_char] == "&" && $text_array[$current_char+1] >= "a" && $text_array[$current_char+1] <= "z") {
                    $special_char = true;
                }
                else {
                    $current_length++;
                }
            }
            $result .= $text_array[$current_char];
            $current_char++;
        }
        if ($current_char == $text_length)
            return $result;
        else
            return $result."...";
    }

    /**
     * Trim a multiple word string to a specific word count
     *
     * @param string $text The text to be trimmed
     * @param int $length Number of word remain
     * @return string Trimmed text
     */
    static function trim_word($text, $length) {
        $text_array = explode(" ",$text, $length + 1);
        if ($length >= count($text_array))
            return $text;
        $end_key = end(array_keys($text_array));
        unset($text_array[$end_key]);
        return implode(" ", $text_array) . "...";
    }

   /**
     * Replaces double line-breaks with paragraph elements.
     *
     * A group of regex replaces used to identify text formatted with newlines and
     * replace double line-breaks with HTML paragraph tags. The remaining
     * line-breaks after conversion become <<br />> tags, unless $br is set to '0'
     * or 'false'.
     *
     * @since 1.0
     *
     * @param string $pee The text which has to be formatted.
     * @param int|bool $br Optional. If set, this will convert all remaining line-breaks after paragraphing. Default true.
     * @return string Text which has been converted into correct paragraph tags.
     */
    static function autop($pee, $br = 1) {
            if ( trim($pee) === '' )
                    return '';
            $pee = $pee . "\n"; // just to make things a little easier, pad the end
            $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee);
            // Space things out a little
            $allblocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)';
            $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee);
            $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
            $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines
            if ( strpos($pee, '<object') !== false ) {
                    $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed
                    $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee);
            }
            $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates
            // make paragraphs, including one at the end
            $pees = preg_split('/\n\s*\n/', $pee, -1, PREG_SPLIT_NO_EMPTY);
            $pee = '';
            foreach ( $pees as $tinkle )
                    $pee .= '<p>' . trim($tinkle, "\n") . "</p>\n";
            $pee = preg_replace('|<p>\s*</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace
            $pee = preg_replace('!<p>([^<]+)</(div|address|form)>!', "<p>$1</p></$2>", $pee);
            $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag
            $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists
            $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
            $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
            $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
            $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
            if ($br) {
                    $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee);
                    $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks
                    $pee = str_replace('<WPPreserveNewline />', "\n", $pee);
            }
            $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee);
            $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
            if (strpos($pee, '<pre') !== false)
                    $pee = preg_replace_callback('!(<pre[^>]*>)(.*?)</pre>!is', 'clean_pre', $pee );
            $pee = preg_replace( "|\n</p>$|", '</p>', $pee );
            //$pee = preg_replace('/<p>\s*?(' . get_shortcode_regex() . ')\s*<\/p>/s', '$1', $pee); // don't auto-p wrap shortcodes that stand alone

            return $pee;
    }

    /**
     * Add trailing slash to a string. This method is used when you want to be 
     * sure that the path ends with a trailing slash.
     *
     * @param string $string
     * @return string A string with trailing slash
     */
    static function add_trailing_slash($string) {
        return (preg_match("/\\/$/", $string)) ? $string : $string."/";
    }

    /**
     * Navigates through an array and adds trailing slashes from the values.
     * This method uses array_map with the callback function is itself. 
     *
     *
     * @param array|string $value The array or string to be added.
     * @return array|string The array or string with trailing slash.
     */
    static function add_trailing_slash_deep($value) {
        $value = is_array($value) ? array_map(array("Owl_Format",'add_trailing_slash_deep'), $value) : self::add_trailing_slash($value);
        return $value;
    }

    /**
     * Remove trailing slash to a string. This method is used when you want to be 
     * sure that the path does not end with a trailing slash.
     *
     * @param string $string
     * @return string A string without trailing slash
     */
    static function remove_trailing_slash($string) {
        return trim(preg_replace("/(\\/)+$/", "", $string));
    }

    /**
     * Navigates through an array and removes trailing slashes from the values.
     * This method uses array_map with the callback function is itself. 
     *
     *
     * @param array|string $value The array or string to be removes.
     * @return array|string The array or string without trailing slash.
     */
    static function remove_trailing_slash_deep($value) {
        $value = is_array($value) ? array_map(array('Owl_Format','remove_trailing_slash_deep'), $value) : self::remove_trailing_slash($value);
        return $value;
    }

    /**
     * Convert a number to day of the week, for example 2 is Monday
     *
     * @param int $day
     * @return string Long day
     */
    static function convert_to_day_of_week($day) {
        if (!is_int($day) || $day < 1 || $day > 7) {
            return $day;
        }
        $convert = array(
            1 => __("Sunday"),
            2 => __("Monday"),
            3 => __("Tuesday"),
            4 => __("Wednesday"),
            5 => __("Thursday"),
            6 => __("Friday"),
            7 => __("Saturday")
        );

        return $convert[$day];
    }

    /**
     * Convert course schedule section
     *
     * @param int $begin Begin section
     * @param int $end End section
     * @return string
     */
    static function convert_section($begin, $end) {
        $string = __("Section");
        $string .= ($begin <= 6) ? " $begin - $end (" . __("Morning") . ")" : " " . ($begin - 6) . " - " . ($end - 6) . " (" . __("Afternoon") . ")";
        
        return $string;
    }

    /**
     * Convert documents array to a more readable content
     *
     * @param array $documents
     * @return string 
     */

    static function convert_document($documents) {
        if (!is_array($documents)) {
            $string = "<b>" . __("Documents") . "</b>: " . __("No data");
        }
        else {
            $pdf = $ppt = $doc = $others = array();
            foreach ($documents as $document) {
                $type = strtolower($document["type"]);
                ${$type}[] = $document;
            }
            $string = '<ul class="document-tree filetree">';
            $string .= '<li><span class="folder open">' . __("Documents") . '</span><ul>';
            foreach (array("pdf", "ppt", "doc", "others") as $type) {
                if (!empty(${$type})) {
                    $string .= '<li class="closed">';
                    $string .= '<span class="folder ' . $type . '">' . __(ucwords($type)) . '</span>';
                    $string .= '<ul>';
                    foreach (${$type} as $file) {
                        $string .= '<li>';
                        $string .= '<span class="file ' . $type . '"><a href="' . Owl_Link::site_url(false) . $file["link"] . '">' . Owl_Format::decode_xml($file["name"]) . '</a></span>';
                        $string .= '</li>';
                    }
                    $string .= '</ul>';
                    $string .= '</li>';
                }
            }
            $string .= '</ul></li>';
            $string .= '</ul>';
        }
        return $string;
    }

    /**
     * Make nice text area content
     *
     * @param string $content
     * @return string
     */
    static function make_nice_textarea_content($content) {
        $content = Owl_Format::make_clickable(Owl_Format::esc_html($content));
        $content = str_replace("\n", "<br />", $content);
        return $content;
    }
}

